#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sqlite3.h>

#import <UIKit/UIImageView.h>
#import <UIKit/UIApplication.h>
#import <SpringBoard/SBApplicationController.h>
#import <SpringBoard/SBApplication.h>
#import <SpringBoard/SBSleepProofTimer.h>
#import <LockInfo/Plugin.h>
#import <AudioToolbox/AudioToolbox.h>

#import "FWCommon.h"
#import "FWDisplayObject.h"
#import "FWTransToolbar.h"
#import "FWToolbarViewCell.h"
#import "FWStatusbarViewCell.h"
#import "FWColorIconView.h"
#import "FWPreferences.h"
#import "FWPlugin.h"
#import "FWListViewDelegate.h"
#import "FWListView.h"
#import "FWRoundedLabel.h"
#import "FWMessageView.h"
#import "FWDotView.h"

static NSString* const kFWOptionsShowProduct = @"isShowProduct";
static NSString* const kFWOptionsDatasourceType = @"datasourceType";

static BOOL const kFWOptionsShowProductDefault = YES;
static FWDatasourceType const kFWOptionsDatasourceTypeDefault = FWDatasourceTypeAutomatic;


static NSString* const kContentKey = @"object";
static NSString* const kContentType = @"type";
static NSString* const kContentSort = @"sortOrder";

NSString* const kUniqueIdStatusBar = @"[[[statusbar]]]";
NSString* const kUniqueIdToolBar = @"[[[toolbar]]]";
NSString* const kUniqueIdMessage = @"[[[message]]]";
NSString* const kUniqueIdInfo = @"[[[info]]]";

static NSTimeInterval const kRefreshTimerInterval = 2;  // Do not perform refreshes more frequently than X secs

static const NSInteger kToolbarHeight = 50;

NSInteger const kStatusLineIndex = -1;
NSInteger const kToolbarIndex = -2;

typedef enum
{
    FWDataTypeElement = 0,
    FWDataTypeStatusRow,
    FWDataTypeToolbarRow,
    FWDataTypeMessageRow,
    FWDataTypeInfoRow
} FWDataType;


@interface LSStatusBarItem : NSObject

-(void)setImageName:(NSString*)name;
-(BOOL)isVisible;
-(void)setVisible:(BOOL)visible;

@end


@implementation FWPlugin

@synthesize currentItems, notificationName, liPlugin, lastUpdate, lastUnconditionalRefresh, fwApplication, fwDatasource, preferences, timer, resourceName, nextScheduledActivity, debugMode, datasourceType, statusbarIcon, messageBoxLine1, messageBoxLine2, messageBoxDetail, nativeOnly, shouldRedisplayOnNextUpdate;


// ******************************************************************************************
#pragma mark -
#pragma mark Initialisation and object management

- (id)initWithApplication:(FWApplication *)app
{
    return self;    
}

- (id)initWithApplication:(FWApplication*)app name:(NSString *)name
{
    if (!self.fwApplication) {
        toolbarRow = -1;
        toolbarOpenedDate = nil;
        self->nativeOnly = NO;

        statusBarOpen = NO;
        messageBarOpen = NO;
        infoBarOpen = NO;
        
        self.fwApplication = app;
        self.shouldRedisplayOnNextUpdate = NO;
        
        self.resourceName = [NSString stringWithString:name];
        self.notificationName = [resourceName stringByAppendingString:@"Notification"];
        
        NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(repeatingNotification:) name:self.notificationName object:nil];
        
        XLog(@"LI:PIEvent: Registered listener for repeating events %@", self.notificationName);
    }        
    
    return self;
}

- (id)initWithPlugin:(LIPlugin*)pi
{
    FWApplication* app = [[[FWApplication alloc] init] autorelease];
    if ((self = [self initWithApplication:app])) {
        XLog(@"LI:%@: Initialising plugin *************", resourceName);

        mutexLock = [[NSObject alloc] init];

        self.liPlugin = pi;
        self.fwApplication.fwPlugin = self;
        self.currentItems = [NSMutableArray arrayWithCapacity:20];
        self.preferences = [[[FWPreferences alloc] initWithPreferences:pi.preferences] autorelease];
        self.lastUpdate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
        self.lastUnconditionalRefresh = [NSDate dateWithTimeIntervalSinceReferenceDate:0];

#ifdef DEBUG
        datasourceType = (FWDatasourceType)[FWPreferences intValue:kFWOptionsDatasourceType
                                                       withDefault:kFWOptionsDatasourceTypeDefault
                                                       preferences:liPlugin.preferences];
#else
        datasourceType = FWDatasourceTypeAutomatic;
#endif
        
        // Register as a datasource for LI
        pi.dataController = self;
        
        XLog(@"LI:%@: Initialised: %@", resourceName, pi);
        DebugLog(@"LI:%@: bundleIdentifier: %@", resourceName, pi.bundleIdentifier);
        DebugLog(@"LI:%@: bundle: %@", resourceName, pi.bundle);
        DebugLog(@"LI:%@: globalBundle: %@", resourceName, pi.globalBundle);
        DebugLog(@"LI:%@: preferences: %@", resourceName, pi.preferences);
        DebugLog(@"LI:%@: globalPreferences: %@", resourceName, pi.globalPreferences);
        DebugLog(@"LI:%@: managedBundles: %@", resourceName, pi.managedBundles);
        
        NSNotificationCenter* center = [NSNotificationCenter defaultCenter];

        [center addObserver:self selector:@selector(timerNotification:)
                       name:LITimerNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LITimerNotification);

        [center addObserver:self selector:@selector(viewReadyNotification:)
                       name:LIViewReadyNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LIViewReadyNotification);

        [center addObserver:self selector:@selector(screenUndimmedNotification:)
                       name:LIUndimScreenNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LIUndimScreenNotification);

        [center addObserver:self selector:@selector(viewStateChangedNotification:)
                       name:LIViewStateDidChangeNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LIViewStateDidChangeNotification);

        // Notification when a window appears - check if the infoshade has appeared
        [center addObserver:self selector:@selector(windowVisibleNotification:)
                       name:@"UIWindowDidBecomeVisibleNotification" object:nil];

        [center addObserver:self selector:@selector(timezoneChangeNotification:)
                       name:NSSystemTimeZoneDidChangeNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, NSSystemTimeZoneDidChangeNotification);
    }

    return self;
}

-(void)dealloc
{
    [currentItems release];
    [liPlugin release];
    [fwApplication release];
    [fwDatasource release];
    [preferences release];
    [toolbarOpenedDate release];
    [resourceName release];
    [notificationName release];
    [nextScheduledActivity release];
    [mutexLock release];
    [messageBoxLine1 release];
    [messageBoxLine2 release];
    
    [super dealloc];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Libstatusbar support

- (void)showLibstatusBarIcon:(BOOL)show
{
    if (!self.statusbarIcon) {
        if ([self.fwDatasource respondsToSelector:@selector(libstatusbarIconName)]) {
            if (NSString* iconName = [self.fwDatasource libstatusbarIconName]) {
                self.statusbarIcon = [[[objc_getClass("LSStatusBarItem") alloc] initWithIdentifier:self.resourceName alignment:2] autorelease];
                XLog(@"LI:%@: Allocated libstatusbar icon %@", self.resourceName, self.statusbarIcon);
                
                [self.statusbarIcon setImageName:iconName];
            }
        }
    }

    if (self.statusbarIcon) {
//        XLog(@"LI:%@: Checking icon status", self.resourceName);
//        if ([self.statusbarIcon isVisible]) {
            XLog(@"LI:%@: Changing libstatusbar visibility = %d", self.resourceName, show);
            [self.statusbarIcon setVisible:show];
//        }
    }
}



// ******************************************************************************************
#pragma mark -
#pragma mark Attribute access

- (void)previewSelected:(LIAction*)action data:(LIData*)data
{
    XLog(@"LI:%@: Preview selected; action = %@; context = %@", resourceName, action, data);
    
    if ([data.uniqueID isEqualToString:kUniqueIdMessage]) {
        FWMessageView* messageData = [[[FWMessageView alloc] init] autorelease];
        FWDetailListView *thePreview = [[[FWDetailListView alloc] initWithFrame:[self previewCompatibleRect]
                                                                           list:[NSArray arrayWithObjects:@"X", nil]
                                                                          index:0
                                                                        caption:@"Message"
                                                                    application:self.fwApplication
                                                                       delegate:messageData] autorelease];
        
        [self.liPlugin showPreview:[self previewCompatibleView:thePreview]];
    }
    else {
        NSUInteger row = [currentItems fwDisplayIndexForKey:data.uniqueID];
        if (row != NSNotFound) {
            UIView* view = [fwDatasource previewWithFrame:[self previewCompatibleRect]
                                                  atIndex:row
                                                 forItems:currentItems
                                                    theme:liPlugin.theme
                                                    prefs:self.preferences
                                              application:fwApplication];
            
            if (view) {
                [self hideToolbar];
                [liPlugin showPreview:[self previewCompatibleView:view]];
            }
        }
    }
}

- (void)accessoryActionSelected:(LIAction*)action data:(LIData*)data
{
    XLog(@"LI:%@: Accessory action selected; action = %@; context = %@", resourceName, action, data);

    NSUInteger row = [currentItems fwDisplayIndexForKey:data.uniqueID];
    if (row != NSNotFound)
        [self performAcessoryAction:[currentItems objectAtIndex:row]];
}


static SEL const kPreviewCallback = @selector(previewSelected:data:);
static SEL const kAccessoryCallback = @selector(accessoryActionSelected:data:);

- (LIData*)dataForDisplayItem:(FWDisplayObject*)item index:(NSUInteger)index
{
    NSDictionary* context = [NSDictionary dictionaryWithObjectsAndKeys:
                             item, kContentKey,
                             [NSNumber numberWithInt:FWDataTypeElement], kContentType,
                             [NSNumber numberWithInt:++index * 10], kContentSort,
                             nil];
    LIData* data = FWLIData(item.key, context);
    data.defaultAction = FWLIAction(self, kPreviewCallback);
    
    if ([self respondsToSelector:@selector(accessoryActionType:)]) {
        FWAccessoryActionType type = [self accessoryActionType:item];
        if (type != FWAccessoryActionTypeNone) {
            data.accessoryAlignment = LIAccessoryAlignmentTopRight;
            if (type == FWAccessoryActionTypeTap) {
                data.accessoryTapAction = FWLIAction(self, kAccessoryCallback);
                data.accessoryTapAction.image = liPlugin.theme.cellDeleteIcon;
            }
            else if (type == FWAccessoryActionTypeHold) {
                data.accessoryHoldAction = FWLIAction(self, kAccessoryCallback);
                data.accessoryHoldAction.image = liPlugin.theme.cellDeleteIcon;
            }
        }
    }
    
    return data;
}

// Must be invoked on main thread
- (void)postUpdatedItem:(FWDisplayObject*)item
{
    // Make sure this happens on the main thread
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(postUpdatedItem:) withObject:item waitUntilDone:YES];
        return;
    }

    item.updateRequired = NO;

    NSUInteger index = [currentItems fwDisplayIndexForKey:item.key];
    if (index != NSNotFound)
        [liPlugin publishData:[NSArray arrayWithObject:[self dataForDisplayItem:item index:index]]];
}
    
// Must be invoked on main thread
- (void)postUpdatedList:(NSArray*)newList
{
    // Make sure this happens on the main thread
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(postUpdatedList:) withObject:newList waitUntilDone:YES];
        return;
    }

    // Close the toolbar if it has been open for a while
    if (toolbarOpenedDate && [[toolbarOpenedDate dateByAddingTimeInterval:5 * 60] isEqualOrEarlierThanDate:[NSDate date]])
        [self hideToolbar];
    
    if (self.isDebugMode)
        XLog(@"LI:%@: Publishing display list", self.resourceName);

    // Build a list of items that are no longer required
    NSMutableArray* deletes = [NSMutableArray arrayWithCapacity:currentItems.count];
    for (FWDisplayObject* current in currentItems) {
        // Is the item missing from the new list?
        if ([newList fwDisplayIndexForKey:current.key] == NSNotFound)
            [deletes addObject:FWLIData(current.key, nil)];
    }
    
    // Build a list of items that are new or need updating
    NSMutableArray* updates = [NSMutableArray arrayWithCapacity:currentItems.count];
    for (NSUInteger newIndex = 0; newIndex < newList.count; ++newIndex) {
        FWDisplayObject* newItem = [newList objectAtIndex:newIndex];
        NSUInteger currentIndex = [currentItems fwDisplayIndexForKey:newItem.key];
        
        if ((currentIndex == NSNotFound) || (currentIndex != newIndex) || newItem.isUpdateRequired || shouldRedisplayOnNextUpdate) {
            [updates addObject:[self dataForDisplayItem:newItem index:newIndex]];
        }
        else {
            // If the object version has changed then we need to display again
            FWDisplayObject* currentItem = [currentItems objectAtIndex:currentIndex];
            if ((currentItem.objectVersion != newItem.objectVersion) || newItem.isUpdateRequired || currentItem.updateRequired)
                [updates addObject:[self dataForDisplayItem:newItem index:newIndex]];
            currentItem.updateRequired = NO;
        }

        newItem.updateRequired = NO;
    }    
    
    self.currentItems = newList;
    self.lastUpdate = [NSDate date];

    // Remove the list of data we don't want
    if (deletes.count > 0) {
        [liPlugin removeData:deletes];
    
        if (self.isDebugMode) {
            for (LIData* data in deletes) {
                XLog(@"LI:%@: Deleted row; key = %@", self.resourceName, data.uniqueID);
            }
        }
    }
    
    if (updates.count > 0) {
        [liPlugin publishData:updates];

        if (self.isDebugMode) {
            for (LIData* data in updates) {
                XLog(@"LI:%@: Updated row; key = %@; index = %d",
                     self.resourceName, data.uniqueID, [[data.context valueForKey:kContentSort] intValue]);
            }
        }
    }
    
    // Need to publish something to cancel the refresh animation
    if ((deletes.count == 0) && (updates.count == 0))
        [liPlugin publishData:nil];
    
    self.shouldRedisplayOnNextUpdate = NO;
}

- (void)updateListOnNewThread:(id)source
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    [self updateList];
    [pool release];
}

- (void)updateList
{
    @synchronized(mutexLock) {
        if (listLock)
            return;
        else
            listLock = YES;
    }
    
    DebugLog(@"LI:%@: Updating list...", resourceName);
    
    FWDatasourceType oldDatasourceType = datasourceType;
#ifdef DEBUG
    datasourceType = (FWDatasourceType)[FWPreferences intValue:kFWOptionsDatasourceType
                                                   withDefault:kFWOptionsDatasourceTypeDefault
                                                   preferences:liPlugin.preferences];
#else
    datasourceType = FWDatasourceTypeAutomatic;
#endif

    if (![fwApplication verifyApplication]) {
        XLog(@"LI:%@: Cannot find application!", resourceName);
        
//        self.showStatusbar = NO;
        [self postUpdatedList:[NSArray array]];
    }
    else if ((oldDatasourceType == datasourceType) && 
             ![fwDatasource shouldUpdateForDate:self.lastUpdate application:self.fwApplication] &&
             ![fwApplication shouldUpdateForDate:self.lastUpdate]) {
        XLog(@"LI:%@: Update is not required", resourceName);
        
        if (shouldRedisplayOnNextUpdate)
            [self triggerRedisplay];
    }
    else {
        DebugLog(@"LI:%@: plugin prefs = %@", resourceName, self.liPlugin.preferences);
        self.preferences = [[[FWPreferences alloc] initWithPreferences:self.liPlugin.preferences] autorelease];
        
        if ([self.fwApplication.applicationDelegate respondsToSelector:@selector(application:updateApplicationPrefs:pluginPrefs:)]) {
            NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:fwApplication.preferencesPath error:NULL];
            NSDate* updated = [attrs objectForKey:NSFileModificationDate];
            
            if ([updated timeIntervalSinceReferenceDate] > [fwApplication.preferencesUpdated timeIntervalSinceReferenceDate]) {
                fwApplication.preferencesUpdated = updated;
                NSDictionary* applicationPrefs = [NSDictionary dictionaryWithContentsOfFile:[fwApplication preferencesPath]];
                [self.fwApplication.applicationDelegate application:fwApplication
                                             updateApplicationPrefs:applicationPrefs 
                                                        pluginPrefs:self.preferences];
                
                DebugLog(@"LI:%@: Application prefs updated at %@ - read", resourceName, updated);
                //DebugLog(@"LI:%@: Application prefs = %@", resourceName, applicationPrefs);
            }
            else
                DebugLog(@"LI:%@: Application preferences not updated since %@ - not read", resourceName, updated);
        }
        
        // Update data and read from database
        NSArray *list = [fwDatasource fetchNewDataForPrefs:preferences application:fwApplication];
        [self postUpdatedList:list];

        if (self.isDebugMode != infoBarOpen) {
            LIData* infoBar = FWLIData(kUniqueIdInfo,
                                    [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithInt:FWDataTypeInfoRow], kContentType,
                                     [NSNumber numberWithInt:99999999], kContentSort,
                                     nil]);
            infoBar.header = YES;
            
            if (self.isDebugMode) {
                infoBar.defaultAction = FWLIAction(self, kPreviewCallback);
                [liPlugin performSelectorOnMainThread:@selector(publishData:)
                                           withObject:[NSArray arrayWithObject:infoBar]
                                        waitUntilDone:YES];
            }
            else
                [liPlugin performSelectorOnMainThread:@selector(removeData:)
                                           withObject:[NSArray arrayWithObject:infoBar]
                                        waitUntilDone:YES];

            infoBarOpen = self.isDebugMode;
        }
        
        // Do we need a messagebar?
        if (messageBoxLine1) {
            LIData* msgBar = FWLIData(kUniqueIdMessage,
                                      [NSDictionary dictionaryWithObjectsAndKeys:
                                       [NSNumber numberWithInt:FWDataTypeMessageRow], kContentType,
                                       [NSNumber numberWithInt:9], kContentSort,
                                       nil]);
            msgBar.header = YES;
            msgBar.defaultAction = FWLIAction(self, kPreviewCallback);
            [liPlugin performSelectorOnMainThread:@selector(publishData:)
                                       withObject:[NSArray arrayWithObject:msgBar]
                                    waitUntilDone:YES];
            messageBarOpen = YES;
        }
        else if (messageBarOpen) {
            [liPlugin performSelectorOnMainThread:@selector(removeData:)
                                       withObject:[NSArray arrayWithObject:FWLIData(kUniqueIdMessage, nil)]
                                    waitUntilDone:YES];
            messageBarOpen = NO;
        }
        
        // Show the plugin statusbar icon if necessary
        if ([self.fwDatasource respondsToSelector:@selector(showLibstatusbarIconForItems:prefs:)])
            [self showLibstatusBarIcon:[fwDatasource showLibstatusbarIconForItems:list prefs:preferences]];
    } 
    
    self.shouldRedisplayOnNextUpdate = NO;
    
    @synchronized(mutexLock) {
        listLock = NO;
    }
}

- (LISectionInfo*)sectionInfo
{
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    LISectionInfo* info = FWLISectionInfo();
    
    info.icon = nil;

    info.detail = [fwDatasource detailForHeader:self.preferences application:self.fwApplication];
    info.detail = @"hello";
    info.title = localize(bundle, [resourceName stringByAppendingString:
                                   [self.preferences boolValue:kFWOptionsShowProduct
                                                   withDefault:kFWOptionsShowProductDefault] ? @"/Long" : @"/Short"]);
    
    info.reloadAction = FWLIAction(self, @selector(refreshPressed:userInfo:));
    info.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:kContentSort ascending:YES]];
    
    return info;
}

- (UIView*)viewForData:(LIData *)data reusableView:(UIView *)view
{
    NSNumber* type = [data.context objectForKey:kContentType];
    if (type.intValue == FWDataTypeElement) {
        FWDisplayObject* obj = [data.context objectForKey:kContentKey];
        
        FWListView* newView = [fwDatasource viewForTheme:liPlugin.theme
                                                existing:view
                                                   prefs:self.preferences
                                             application:self.fwApplication];
        
        [fwDatasource setDetailsUsingView:newView
                                  forItem:obj
                                    theme:liPlugin.theme
                                    prefs:self.preferences
                              application:self.fwApplication];
        
        return newView;
    }
    else if (type.intValue == FWDataTypeInfoRow) {
        FWListView* newView = [[[FWListView alloc] initWithFrame:CGRectMake(0, 0, 320, 0) theme:liPlugin.theme] autorelease];
        
        newView.detailText.style = liPlugin.theme.detailStyle;
        newView.detailText.hidden = NO;
        [newView.detailText setNeedsDisplay];
        newView.summaryText.style = liPlugin.theme.detailStyle;
        newView.summaryText.hidden = NO;
        [newView.summaryText setNeedsDisplay];
        newView.highlightText.hidden = YES;
        newView.dot.hidden = YES;
        newView.drawBorder = YES;

        [fwDatasource setDetailsUsingView:newView
                                  forItem:nil
                                    theme:liPlugin.theme
                                    prefs:self.preferences
                              application:self.fwApplication];

        [newView setNeedsLayout];
        
        return newView;
    }
    else if (type.intValue == FWDataTypeMessageRow) {
        FWListView* newView = [[[FWListView alloc] initWithFrame:CGRectMake(0, 0, 320, 10) theme:liPlugin.theme] autorelease];
        
        newView.summaryText.style = liPlugin.theme.summaryStyle;
        newView.summaryText.hidden = NO;
        newView.summaryText.text = messageBoxLine1;
        [newView.summaryText setNeedsDisplay];

        newView.detailText.style = liPlugin.theme.summaryStyle;
        newView.detailText.hidden = NO;
        newView.detailText.text = messageBoxLine2;
        [newView.detailText setNeedsDisplay];

        newView.highlightText.hidden = YES;
        newView.dot.hidden = YES;
        newView.drawBorder = YES;

        [newView setNeedsLayout];
        
        return newView;
    }
    else if (type.intValue == FWDataTypeToolbarRow) {
        FWToolbarViewCell* toolbar;
        if ([view isMemberOfClass:[FWToolbarViewCell class]]) {
            toolbar = (FWToolbarViewCell*)view;
            [toolbar setItems:[self toolbarItems]];
        }
        else {
            toolbar = [[[FWToolbarViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, kToolbarHeight)
                                                          theme:self.liPlugin.theme
                                                   toolbarItems:[self toolbarItems]] autorelease];
        }
        
//        NSBundle* lockinfo = [NSBundle bundleWithPath:@"/Library/LockInfo"];
//#ifdef DEVELOPMENT_MODE
//        lockinfo = [NSBundle bundleForClass:[self class]];
//#endif
        
        [toolbar setBackgroundColor:[UIColor blackColor]];
        
        return toolbar;
    }
    else if (type.intValue == FWDataTypeStatusRow) {
        FWStatusbarViewCell* statusView;
        if ([view isMemberOfClass:[FWStatusbarViewCell class]]) {
            statusView = (FWStatusbarViewCell*)view;
            [statusView setItems:[self statusbarItems]];
        }
        else {
            CGFloat height = [FWStatusbarViewCell heightForStatusbarWithTheme:self.liPlugin.theme];
            statusView = [[[FWStatusbarViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, height)
                                                               theme:self.liPlugin.theme 
                                                        toolbarItems:[self statusbarItems]] autorelease];
        }
        
        if ([self respondsToSelector:@selector(performSwipeAction:)]) {
            statusView.swipeTarget = self;
            statusView.swipeAction = @selector(performStatusbarSwipe:);
        }
        
        NSInteger indent = [self statusbarIndent];
        NSString* caption = [self statusbarCaptionValue];
        [statusView setInformationWithTheme:self.liPlugin.theme indent:indent label:caption];

        NSString* badge = [self statusbarBadgeValue];
        [statusView setBadgeValue:badge hidden:(badge == nil)];

        // Update information
        if (NSString* icon = (caption.length > 0) ? [self statusbarCaptionIcon2] : nil) {
            statusView.labelIcon3.icon = [[NSBundle bundleForClass:[self class]] pathForResource:icon ofType:@"png"];
            statusView.labelIcon3.hidden = NO;
        }
        else
            statusView.labelIcon3.hidden = YES;

        // Update information
        if (NSString* icon = (caption.length > 0) ? [self statusbarCaptionIcon] : nil) {
            statusView.labelIcon2.icon = [[NSBundle bundleForClass:[self class]] pathForResource:icon ofType:@"png"];
            statusView.labelIcon2.hidden = NO;
        }
        else
            statusView.labelIcon2.hidden = YES;

        UIImage* subheaderImage = [[self.liPlugin.theme subheaderBackground] stretchableImageWithLeftCapWidth:0 topCapHeight:0];
        [statusView setBackgroundWithImage:subheaderImage];

        [statusView setNeedsLayout];
        return statusView;
    }
    else
        return nil;
}

- (CGFloat)viewHeightForData:(LIData *)data withWidth:(CGFloat)width
{
    NSNumber* type = [data.context objectForKey:kContentType];
    CGFloat height = 999;
    if (type.intValue == FWDataTypeElement) {
        FWDisplayObject* obj = [data.context objectForKey:kContentKey];
        height = [fwDatasource heightForItem:obj theme:liPlugin.theme prefs:self.preferences application:self.fwApplication] + kFWListViewSeparatorSpace;
    }
    else if (type.intValue == FWDataTypeToolbarRow)
        height = kToolbarHeight + kFWToolbarViewSeparatorSpace;
    else if (type.intValue == FWDataTypeStatusRow)
        height = [FWStatusbarViewCell heightForStatusbarWithTheme:self.liPlugin.theme] + kFWToolbarViewSeparatorSpace;
    else if (type.intValue == FWDataTypeMessageRow) {
        height = liPlugin.theme.summaryStyle.font.leading;
        if (messageBoxLine2)
            height *= 2;
        height += 2 * kFWListViewBorderInset + kFWListViewSeparatorSpace;
    }
    else if (type.intValue == FWDataTypeInfoRow)
        height = 2 * liPlugin.theme.summaryStyle.font.leading + kFWListViewBorderInset + kFWListViewSeparatorSpace;
    
    //DebugLog(@"LI:%@: Size for row %@ = %f", resourceName, data, height);
    return height;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Data reload support

- (void)scheduleUnconditionalRefresh
{
    DebugLog(@"LI:%@: unconditionalRefresh scheduled ############", resourceName);
    
    self.lastUpdate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
}

- (void)triggerUnconditionalRefresh
{
    DebugLog(@"LI:%@: unconditionalRefresh triggered ############", resourceName);
    
    if ([[NSDate date] timeIntervalSinceDate:self.lastUnconditionalRefresh] >= kRefreshTimerInterval) {
        self.lastUpdate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
        self.lastUnconditionalRefresh = [NSDate date];
        [self performSelectorInBackground:@selector(updateListOnNewThread:) withObject:self]; 
    }
#ifdef DEBUG
    else
        DebugLog(@"LI:%@: unconditionalRefresh ignored - too frequent", resourceName);
#endif
}

- (void)refreshPressed:(LIAction*)action userInfo:(NSDictionary*)userInfo
{
    DebugLog(@"LI:%@: refresh pressed ############", resourceName);
    
    [self hideToolbar];
    
    self.lastUpdate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
    [self updateList];
    [self triggerRedisplay];
    [liPlugin updateSectionInfo:[self sectionInfo] reloadData:NO];
//    [liPlugin updateSectionInfo:[self sectionInfo] reloadData:YES];
}

- (void)redisplayListOnNewThread:(id)source
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    [self triggerRedisplay];
    [pool release];
}

- (void)triggerRedisplay
{
    XLog(@"LI:%@: Triggering redisplay", resourceName);

    NSArray* updates = [NSArray arrayWithArray:currentItems];
    for (FWDisplayObject* item in updates)
        item.updateRequired = YES;
    
    [self postUpdatedList:updates];
}

- (void)updateListForNotification
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    [self updateList];
    [pool release];
}

- (void)timerNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Timer notification - plugin disabled", resourceName);
        return;
    }

    XLog(@"LI:%@: Timer notification ############", resourceName);
    
    [self updateListForNotification];
}

- (void)timezoneChangeNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Timezone change notification - plugin disabled", resourceName);
        return;
    }
    
    DebugLog(@"LI:%@: Timezone change notification ############", resourceName);
    
    [NSTimeZone resetSystemTimeZone];
    [self triggerUnconditionalRefresh];
}

- (void)viewStateChangedNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: View state changed notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: View state changed notification ############ %@", resourceName, notif);
    
//    [self updateListForNotification];
}

- (void)viewReadyNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: View ready notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: View ready notification ############", resourceName);
    
    [self updateListForNotification];
}

- (void)screenUndimmedNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Screen undimmed notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: Screen undimmed notification ############", resourceName);
    
    [self hideToolbar];
    [self updateListForNotification];
}

static Class const liWindowShade = objc_getClass("LIShadeWindow");

- (void)windowVisibleNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Windowshade visible notification - plugin disabled", resourceName);
        return;
    }
    
    if (liWindowShade && [notif.object isMemberOfClass:liWindowShade]) {
        XLog(@"LI:%@: Windowshade visible notification ############", resourceName);
        
        [self updateListForNotification];
    }
}

- (void)timerTriggered:(NSTimer*)timer
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Scheduled notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: Scheduled notification ############", resourceName);

    self.timer = nil;
    if (nextScheduledActivity) {
        if ([[NSDate date] isEqualOrLaterThanDate:nextScheduledActivity]) {  
            NSDate* date = [nextScheduledActivity retain];
            
            // We've reached the scheduled time
            [nextScheduledActivity release];
            nextScheduledActivity = nil;
            
            if ([self respondsToSelector:@selector(scheduledEventNotification:)])
                [self scheduledEventNotification:date];
            else
                [self updateListForNotification];

            [date release];
        }
        else
            [self scheduleNextTimerEvent:nextScheduledActivity];
    }
}


const NSInteger kTimeInterval1 = 30 * 60;
const NSInteger kTimeInterval2 = 15 * 60;
const NSInteger kTimeInterval3 = 10 * 60;
const NSInteger kTimeInterval4 = 5 * 60;

- (void)scheduleNextTimerEvent:(NSDate*)date
{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
    
    if (nextScheduledActivity)
        [nextScheduledActivity release];
    nextScheduledActivity = [date retain];
    
    // Try to handle the fact that timers may be late during deep sleep. Try to sneak up on
    // the time we want by setting timers closer to the date. The idea is that if one is late
    // then we will still be before the required time.
    NSTimeInterval remaining = [date timeIntervalSinceNow];
    if (remaining <= 0) {
        XLog(@"LI:%@: Scheduled time %@ is in the past - ignoring", resourceName, date);
        return;
    }
    else if (remaining >= kTimeInterval1)
        date = [date addTimeInterval:-kTimeInterval1];
    else if (remaining >= kTimeInterval2)
        date = [date addTimeInterval:-kTimeInterval2];
    else if (remaining >= kTimeInterval3)
        date = [date addTimeInterval:-kTimeInterval3];
    else if (remaining >= kTimeInterval4)
        date = [date addTimeInterval:-kTimeInterval4];

    XLog(@"LI:%@: Scheduling next wake event for %@ at %@", resourceName, nextScheduledActivity, date);

    [self performSelector:@selector(timerTriggered:)
               withObject:nil
               afterDelay:[date timeIntervalSinceNow]
                  inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Statusbar support

- (void)scheduleSetStatusbarVisible:(NSNumber*)visible
{
    // Make sure we are on the main thread
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(scheduleSetStatusbarVisible:) withObject:visible waitUntilDone:YES];
        return;
    }
    
    DebugLog(@"LI:%@: Setting statusbar visibility to %i", resourceName, visible.intValue);

    if (visible.boolValue || (statusBarOpen != visible.boolValue)) {
        LIData* data = FWLIData(kUniqueIdStatusBar,
                                [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithInt:FWDataTypeStatusRow], kContentType,
                                 [NSNumber numberWithInt:0], kContentSort,
                                 nil]);
        data.header = YES;
        
        if (!visible.boolValue)
            [self.liPlugin removeData:[NSArray arrayWithObject:data]];
        else
            [self.liPlugin publishData:[NSArray arrayWithObject:data]];
        
        statusBarOpen = visible.boolValue;
    }
}

- (BOOL)isStatusbarVisible
{
    return statusBarOpen;
}

- (void)setStatusbarVisible:(BOOL)visible
{
    if (visible != statusBarOpen)
        [self scheduleSetStatusbarVisible:[NSNumber numberWithBool:visible]];
}

- (void)updateStatusbar
{
    if (statusBarOpen)
        [self scheduleSetStatusbarVisible:NSNumberYES];
}

- (void)performStatusbarSwipe:(NSNumber*)direction
{
    [self performSwipeAction:(UISwipeGestureRecognizerDirection)direction.intValue];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Toolbar support

- (void)scheduleSetToolbarIndex:(NSNumber*)index
{
    // Make sure we are on the main thread
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(scheduleSetToolbarIndex:) withObject:index waitUntilDone:YES];
        return;
    }
    
    DebugLog(@"LI:%@: Setting toolbar to index %i", resourceName, index.intValue);
    
    if (index.intValue == toolbarRow)
        return;
    
    // Remove the curent toolbar
    if (toolbarRow >= 0)
        [self.liPlugin removeData:[NSArray arrayWithObject:FWLIData(kUniqueIdToolBar, nil)]];
    
    toolbarRow = index.intValue;
    
    if (toolbarRow >= 0) {
        LIData* data = FWLIData(kUniqueIdToolBar,
                                [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithInt:FWDataTypeToolbarRow], kContentType,
                                 [NSNumber numberWithInt:toolbarRow * 10 + 1], kContentSort,
                                 nil]);
        data.header = YES;

        [self.liPlugin publishData:[NSArray arrayWithObject:data]];
    }
    
    [toolbarOpenedDate release];
    if (toolbarRow < 0)
        toolbarOpenedDate = nil;
    else
        toolbarOpenedDate = [[NSDate date] retain];
}

- (void)hideToolbar
{
    if (toolbarRow >= 0)
        [self scheduleSetToolbarIndex:[NSNumber numberWithInt:-1]];
}

- (void)setToolbarIndex:(NSInteger)index
{
    [self scheduleSetToolbarIndex:[NSNumber numberWithInt:index]];
}

- (void)setToolbarIndex:(NSInteger)index updatingRow:(NSInteger)row
{
    [self postUpdatedItem:[currentItems objectAtIndex:row]];
    [self setToolbarIndex:index];
}

- (BOOL)isToolbarVisible
{
    return toolbarRow >= 0;
}

- (NSInteger)toolbarIndex
{
    return toolbarRow;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Preview support

- (UIView*)previewCompatibleView:(UIView*)view
{
    CGSize screenSize = FWGetCurrentScreenSize();
    UIView* container = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height)] autorelease];
    container.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    
#ifdef DEVELOPMENT_MODE
    container.backgroundColor = [UIColor yellowColor];
#else
    container.backgroundColor = [UIColor clearColor];
#endif
    
    [container addSubview:view];
    
    return container;
}

- (CGRect)previewCompatibleRect
{
    CGSize screenSize = FWGetCurrentScreenSize();

#ifdef DEVELOPMENT_MODE
    return CGRectMake(0, 0, screenSize.width, screenSize.height - 20);
#else
    return CGRectMake(0, 20, screenSize.width, screenSize.height - 20);
#endif
}


- (void)resetMessageBox
{
    self.messageBoxDetail = nil;
    self.messageBoxLine1 = nil;
    self.messageBoxLine2 = nil;
}

- (void)setMessageBox:(NSString*)line1 line2:(NSString*)line2
{
    self.messageBoxLine1 = line1;
    self.messageBoxLine2 = line2;
    self.messageBoxDetail = nil;
}

- (void)setMessageBox:(NSString*)line1 line2:(NSString*)line2 detail:(NSString*)detail
{
    self.messageBoxLine1 = line1;
    self.messageBoxLine2 = line2;
    self.messageBoxDetail = [[detail copy] autorelease];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Notifications and alarms support

- (void)repeatingNotification:(NSNotification*)notif
{
    XLog(@"LI:%@: Repeating event notification ############", resourceName);

    if ([self respondsToSelector:@selector(repeatingEventNotification:)]) {
        XLog(@"LI:%@: Notification event", resourceName);
        [self repeatingEventNotification:notif];
    }
}

@end
